Shiny Integration with Extended user authentication

Myo Minn Oo

6/10/23

A little about myself:
Myo Minn Oo

Hello!

  • Postdoctoral Fellow at the University of Manitoba.
  • Epidemiologist, doing data science and bioinformatics with a background in implementation science, and medical training.
  • Keen to learn more about R programming and visualization.
  • Enjoy learning new and Shiny things!
  • Broad interests in infectious diseases (HIV, Sexually Transmitted Infections, TB, etc) and data visualization with R.
  • Packages: shinyAuthX, mBudget, mStats, u5mr
  • Projects: visit my website

How I keep going learning and practicing?

  • R for Researchers monthly events for learning and networking at UoM, Winnipeg. It’s a good environment for practicing organizing tutorials.
  • Myanmar R User Group online weekly events: organize and facilite R-related tutorials.
  • Integrate R into my daily work: create data products, dashboards, and analyses.
  • Build things in R outside work: github page in quarto & R, CV in R, Shiny Apps.

A Personal Journey to Personal Finance Management

How did it start?

  • Personal finance journey in good old MS Excel with googledrive backup.
  • Wanted something completely online.

My wish-list

  • Online storage
  • User authentication for secure access
  • CRUD: create, retrieve, update, delete
  • Free of charge!
  • Learn and Share R
  • Do fun projects
  • Use what I learn in my work

Result of many first attempts

  • Basic Shiny functions
  • html, css
  • Permanent storage: mongoDB via mongolite
  • Communication: Email through blastula

What’s missing?

  • Messy codes

  • User authentication - not standardized (not modular)

  • Sign-up feature - email and mongoDB credentials are hard-coded!!!

  • No password recovery function for users (me and my wife!)

  • Buggy

  • shinyauthr and shinymanager - very useful packages, yet no sign-up or password recovery functionalities

  • Bias: Other packages with similar functionalities might exist!

shinyAuthX

Or Making my life more complicated!

Shiny Integration with database and email functionalities

Created in draw.io

Installation


Vignette: https://myominnoo.github.io/shinyAuthX

# install.packages("remotes")
remotes::install_github("myominnoo/shinyAuthX")

Basic: sign-in and sign-out

library(shiny)
library(shinyAuthX)

# dataframe that holds usernames, passwords and other user data
users_base <- create_dummy_users()
users_base
# A tibble: 3 × 6
  date_created        username password                  name  email permissions
  <dttm>              <chr>    <chr>                     <chr> <chr> <chr>      
1 2023-06-08 01:49:29 admin    $7$C6..../....rLHXP8EXaC… Admin admi… admin      
2 2023-06-08 01:49:29 user1    $7$C6..../....bOBbF4NeEv… User… user… standard   
3 2023-06-08 01:49:29 user2    $7$C6..../....gggTNcZikU… User… user… standard   

Two lines on UI side

ui <- fluidPage(
  # add signout button UI
  div(class = "pull-right", signoutUI(id = "signout")),

  # add signin panel UI function without signup or password recovery panel
  signinUI(id = "signin", .add_forgotpw = FALSE, .add_btn_signup = FALSE),

  # setup output to show user info after signin
  verbatimTextOutput("user_data")
)

A few codes for server side

server <- function(input, output, session) {

    # call the signout module with reactive trigger to hide/show
    signout_init <- signoutServer(
        id = "signout",
        active = reactive(credentials()$user_auth)
    )

  # call signin module supplying data frame,
  credentials <- signinServer(
    id = "signin",
    users_db = users_base,
    sodium_hashed = TRUE,
    reload_on_signout = FALSE,
    signout = reactive(signout_init())
  )

  output$user_data <- renderPrint({
    # use req to only render results when credentials()$user_auth is TRUE
    req(credentials()$user_auth)
    str(credentials())
  })
}

mongoDB via mongolite

library(mongolite)
## default mongodb database server for testing: works only with `mtcars`
con <- mongo("mtcars", url = "mongodb+srv://readwrite:test@cluster0-84vdt.mongodb.net/test")
## remove any existing rows
con$drop()
## check
con$count()
# add users_base to con
shinyAuthX::create_dummy_users() |>
    con$insert()
## check again
con$count()
## retrieve data
con$find(fields = '{}')

Pass con to users_db args

 # call signin module supplying data frame,
    credentials <- signinServer(
        id = "signin",
        users_db = con$find('{}'), ## add mongodb connection instead of tibble
        sodium_hashed = TRUE,
        reload_on_signout = FALSE,
        signout = reactive(signout_init())
    )

Extended Sign-up and Password-reset modules

ui <- fluidPage(
    # add signout button UI
    div(class = "pull-right", signoutUI(id = "signout")),

    # add signin panel UI function with signup panel
    signinUI(id = "signin", .add_forgotpw = TRUE, .add_btn_signup = TRUE),
    # add signup panel
    signupUI("signup"),
    # add password-reset panel
    forgotpwUI("pw-reset"),

    # setup output to show user info after signin
    verbatimTextOutput("user_data")
)

Just two more lines on server side

  • Pass credentials & con to the other parts!
server <- function(input, output, session) {

    # call the signout module with reactive trigger to hide/show
    signout_init <- signoutServer(
        id = "signout",
        active = reactive(credentials()$user_auth)
    )

    # call signin module supplying data frame,
    credentials <- signinServer(
        id = "signin",
        users_db = con$find('{}'), ## add mongodb connection instead of tibble
        sodium_hashed = TRUE,
        reload_on_signout = FALSE,
        signout = reactive(signout_init())
    )

    # call signup module supplying credentials() reactive and mongodb
    signupServer(
        id = "signup", credentials = credentials, mongodb = con
    )
    # call password-reset module supplying credentials() reactive and mongodb
    forgotpwServer(
        id = "pw-reset", credentials = credentials, mongodb = con
    )

    output$user_data <- renderPrint({
        # use req to only render results when credentials()$user_auth is TRUE
        req(credentials()$user_auth)
        str(credentials())
    })
}

Communicate users via Email

  • Send verification codes for sign-up and password reset.
  • Welcome emails.

blastula package - Compose emails: compose_email() - Send emails: smtp_send() - credential file: creds_file()

Create Email Template

template <- shinyAuthX::email_template(
    creds_file = blastula::creds_file("path/outlook_creds"),
    from = "user@email.com"
)


## changes to server 
server <- function(input, output, session) {
  
  ## other codes here
  
    # call signup module supplying credentials() reactive and mongodb
    signupServer(
        id = "signup", credentials = credentials, mongodb = con, email = template
    )
    # call password-reset module supplying credentials() reactive and mongodb
    forgotpwServer(
        id = "pw-reset", credentials = credentials, mongodb = con, email = template
    )
  
}

Email Template

Planning my budget app

mBudget App

mBudget App: A few other steps

  • Golem framework
  • Rapid prototyping
  • shinipsum and fakir
  • UI Integration with authentication modules
  • Packages from appsilon: semantic.dashboard, shiny.fluent, and others - compatibility issues
  • Examples from shinyauthr using shinydashboard to integrate authentication
  • Final decision: bs4Dash for app UI
  • Email module with blastula
  • Outlook more feasible; Gmail not so much!
  • Database integration with mongoDB
  • via mongolite
  • Development of the rest of the app

Demo

Demo

Summary

  • Learn R by doing fun projects!
  • Don’t be afraid to make mistakes!
  • Practice makes your codes better!
  • Share with others!
  • My now wish-list: write more unit tests, automate deployment process, version control with docker, and so on.